Networking
Volume Number: 1
Issue Number: 11
Column Tag: C Workshop
Programmer's Guide to Networking 
What Apple Doesn't Tell You!
By Robert B. Denny, Timothy T. Coad, Alisa Systems, Inc., MacTutor
Editorial Board
It is deceptively easy to get started with AppleTalk. But once you get into
non-trivial applications, things get difficult rather quickly. The drivers are poorly
documented, there are a number of "features" and limitations that don't come to light
right away, and there are a few bugs in the ROM that show up when using AppleTalk.
When I set about to write this article, I was completely surprised at the amount
of information that I had digested over the last six months of working with AppleTalk.
Back then, Tim Coad, Mike Schuster and I were given a set of pre-release drivers and a
copy of Inside AppleTalk which had no documentation on the drivers at all.
We were desperate. We disassembled and commented the drivers. It started out
as a "quickie" project that quickly became a time-burning obsession. We learned a lot
about the internals of AppleTalk, and finally completed the project after about a month.
We then set about writing some "practice" applications and desk accessories. We
all spent a good deal of time disassembling and tracing in the ROM. There are bugs in
the current Mac ROM that have to do with handling asynchronous I/O and completion
routines. The annoying part was that when we called Apple about the bugs, they said
"Oh yes, we know about that one and its fixed in the new ROMS." I wonder why no
mention was made in the software supplements? In fact, I have yet to see any
information from Apple about known bugs.
Anyway, the information we had gleaned from disassembling the AppleTalk
drivers turned out to be of immense value in developing applications. There are many
subleties in using the MPP and ATP driver services, particularly in asynchronous I/O
and completion routines.
I will try to share some of this information with you. To whet your appetite,
Figure 1 shows the layout of the various components of the AppleTalk Manager
(drivers and resources). The arrows are meant to show pointer relationships.
As usual for this column, the content is geared to the advanced programmer who
is familiar with the Mac I/O conventions and services of the Device Manager. If you
plan to do any development using AppleTalk, you'll need Inside AppleTalk, available
from Apple. At a minimum, you'll need the AppleTalk Manager Programmer's Guide,
furnished with the May/June "software supplement" for developers. Earlier versions
contain many errors.
Basic Concepts: Requestors and Responders
Before diving in, let's look at the sequence of events of a typical requestor and
responder pair using ATP and NBP. We'll use the dial-a-fortune example described
last month. The server accepts ATP requests and responds with a fortune cookie
message.
First, the responder (dial-a-fortune server ) opens an ATP responding socket,
letting ATP assign a random (dynamic) socket number. Then it registers itself using
NBP as an object of type "Dial-A-Fortune" with some arbitrary name field. At this
point, the server can be located by name using NBP. Finally, the server issues an ATP
"getRequest", making it ready to receive incoming fortune requests.
The requestor (client) must locate a server by name, since socket numbers are
normally dynamically assigned. So it asks NBP to "Tell me the internet addresses of all
objects of type 'Dial-A-Server'." Then it picks out a server and uses the
corresponding internet address to issue a request. Note that the requestor does not
"open" a socket with ATP. Instead, ATP opens a new socket automatically every time a
request is issued, and closes it when the response is received or an error occurs. The
requestor client need not be concerned with socket numbers.
The server responds to the request with a fortune cookie message. Then it issues
another getRequest, making it ready for the next incoming request. Meanwhile the
requestor receives the cookie response and displays it on the screen, or whatever.
That's all there is to it.
AppleTalk Data Structures in C
Last month's C Workshop covered the overall architecture of the AppleTalk
network and presented the "dial-a-fortune" server application written (mostly) in C.
There was not enough room to publish the file "ATALK.H", which contains the structure
and symbol definitions needed to compile the example.
Much can be learned from studying the data structures involved when using the
services of the AppleTalk drivers MPP and ATP. Therefore, we'll discuss some
practical aspects of using AppleTalk I/O services while displaying the relevant C
structure definitions. We shall limit the discussion to AppleTalk Transaction Protocol
(ATP) and Name Binding Protocol (NBP). Most applications need only these services.
Other parts of the AppleTalk manager are useful only in rare situations.
Common Data Structures
#define byte unsigned char
#define word unsigned short
#define longword unsigned long
First, we define some new type names, "byte", "word" and "longword", which
simply make the other structure definitions more easily readable.
/* AppleTalk Network Address Block */
word net; /* Network number */
byte node; /* Node number */
byte skt; /* Socket number */
The Address Block contains the complete specification of an internetwork
address. The network number is used for routing; once a packet gets to the destination
net all nodes on that net can "hear" it, and the proper one picks it up and delivers it to
the indicated socket in that node. The Address Block is used throughout for addressing
network visible entities.
/* Unit and Reference Numbers */
#define mppUnitNum 9
#define atpUnitNum 10
#define mppRefNum -(mppUnitNum+1)
#define atpRefNum -(atpUnitNum+1)
Using Name Binding Protocol requires that you issue I/O requests to the '.MPP'
device. Likewise, using AppleTalk Transaction Protocol requires I/O to the '.ATP'
device. Their Device Manager unit and reference numbers are fixed as defined below:
Definitions for Name Binding Protocol
/* Entity Name Tuple */
addrBlock addr; /* Internet address */
byte enumerator; /* Enumerator */
byte entity[33*3]; /* Entity Name */
The Name Binding Protocol provides the facilities for registering a
network-visible entity (net, node and socket, the internet address) by name, and for
locating registered entities by name and getting their net address. Registration assigns
an entity name to a given internet address, and lookup returns one or more internet
addresses given an entity name specification.
The entity name consists of three fields: the name, the type and the zone. Each
field is a counted (Pascal) string up to 32 bytes in length. The name and type fields in
a lookup request may contain an equal sign ("=") to indicate all names or types. The
zone field always contains an asterisk ("*"), meaning "this zone". Zone support will
be implemented in the future. The individual fields are Pascal strings, and the
delimiters are placed in between and are not counted. A complete entity name consists
of those three fields separated by delimiters as shown below:
name : type @* (zone is always "*")
Entity names and their corresponding internet addresses form a pair called a
tuple. When registering a new entity, you supply a tuple with the nbpRegister I/O
request. When doing an nbpLookup, tuples are sent to you from other systems on the
internet which have entities registered that match your lookup specification. The
tuple structure shown below contains an "enumerator" field, which is used internally
by the NBP software to speed up filtering of duplicate lookup responses from other
nodes. You need not be concerned about this.
/* Names Table Entity Queue Element */
struct _NBPElem *link; /* Next element */
NBPTuple tuple; /* Entity tuple */
NBP keeps the names registered on its local node in a linked list. The tuple you
supply when registering a name must be preceded by a longword that NBP will use as a
list link. Together, the tuple and link longword are called a Names Table Queue
Element. The list doesn't really function as a queue; there is no implied ordering in the
list. However, the Mac documentation frequently refers to order-free linked lists as
queues, so we will follow Apple's conventions.
/* Retry Specification */
byte interval; /* Retry interval, ticks/8 */
byte count; /* Retry count */
NBP allows specification of retry interval and count on their I/O requests. The
retry interval is specified in 8-tick units. Setting these values requires more careful
thought than you might suppose at first.
If a remote node is too busy, it might not see a particular NBP lookup request
(which may be a "real" request or a "verification" request made when checking the
uniqueness of a name during registration). Therefore each "lookup" must really be a
sequence of broadcast lookup requests, in the hope that everyone out there will see at
least one of them and reply as required.
If you specify too few retries, or if the retries are too close together in time,
some stations may miss the activity. This can result in your not finding something, or
worse, registering a duplicate name. If you try too many times, it clogs the network
with unnecessary traffic. If the retries span too long a time, your user will get sick of
waiting for the lookup process to complete. This is the delay you get when the message
"Looking for LaserWriter" appears on the screen during LaserWriter printing.
Name binding services are handled by the MPP driver, part of the AppleTalk
Manager package. All NBP requests are mapped as control calls to the driver. The
definition of the I/O parameter block used for MPP calls is shown below.
NOTE: Never alter the contents of a parameter block used with AppleTalk until the
corresponding I/O request completes. You must use a separate parameter block for
each concurrent outstanding AppleTalk I/O request.
typedef struct _MPPBlock
{
struct __MPBlock *ioLink; /* List link */
short ioType; /* Next 3 used by Device Mgr */
short ioTrap;
Ptr ioCmdAddr;
ProcPtr ioCompletion; /* -> Completion routine */
short ioResult; /* Result code from req. */
char *ioFileName; /* Unused *./
short ioVRefNum; /* Unused */
short ioRefNum; /* Driver RefNum (-10) */
short csCode; /* Op-code (see below) */
MPPCsParam csParam; /* (see below) */
/* NBP csCodes */
#define _nbpLoad 249 /* Load NBP */
#define _nbpConfirm 250 /* Confirm name */
#define _nbpLookup 251 /* Lookup name */
#define _nbpRemove 252 /* Remove name */
#define _nbpRegister 253 /* Register name */
#define _nbpKill 254 /* Kill NBP req */
#define _nbpUnload 255 /* Unload NBP */
The structure is typical for a control call as documented in the Device Manager
section of Inside Macintosh. The csCode field selects which NBP service is being
requested. The csParam field is a union of function-dependent structures relating to
the particular service. This union is described later.
The _nbpLoad and _nbpUnload functions are normally not needed, as the
AppleTalk drivers are loaded during startup on a 512K Mac. I haven't tried to do
AppleTalk development on a 128K Mac, but I suspect it would be frustrating.
The csParam "field" is really a function-dependent continuation of the parameter
block. In C, it's easiest to define this as a union, with each member struct defining the
parameter layout for the corresponding driver service. It is not used with the nbpKill
function, which simply kills any outstanding NBP I/O.
/* MPP Control Block */
struct /* For "Register Name" (csCode=253) */
Retry retry; /* Retry int. and count */
NBPElem *elem; /* Pointer to entity spec */
byte verify; /* Whether to verify */
byte unused;
The first struct is used with csCode = _nbpRegister. You supply the retry
interval and count. This determines how hard NBP tries to check for a duplicate name.
A good starting point is 16 ticks (retry interval = 16/8 = 2) and 20 retries. This
means it will take 5.3 seconds to register a name. Hopefully, registering a name is an
infrequent operation. What you don't want is a duplicate name, so it's worth it to try
over and over again to be sure your name isn't in use.
You also supply the entity name table queue element, a tuple with a link field as
described previously. Finally, you may disable the name uniqueness check. If you
know that the name you want to register is unique, you can bypass the check and save
time. Not recommended.
struct /* For Remove Name (csCode = 252) */
Entity *entity; /* Pointer to entity spec */
To remove a name, supply the entity specification. Nothing else is needed.
struct /* For Lookup (csCode = 251) */
Retry retry; /* Retry int. and count */
Entity *entity; /* Pointer to entity spec */
byte *retBuffPtr; /* Pointer to resp. buffer */
word retBuffSize; /* Size of resp. buffer */
word maxToGet; /* Max responses to get */
word numGotten; /* No. actually gotten */
The lookup is the most complex NBP operation. You supply the retry info, an
entity specification, a place to put the tuples that you get back, and the maximum
number of tuples that place can hold. When you issue the lookup request, NBP starts
yelling all over the network "Hey, anyone out there have any names matching this
description?" The retry information you supply governs the number of yells and the
elapsed time between them.
If the entity specification contains any wildcard fields (name and/or type is
"="), you may get back many tuples. Suppose you wanted to find all of the objects of
type "Phone". You would supply an entity string of: